/*  (c) 2008 Benjamin Schroeter
 *  This file is part of JaDAL - Just another DSL-Tools Addon Library
 *  and licensed under the New BSD License
 *  For more information please visit http://www.codeplex.com/JaDAL */
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;

namespace BenjaminSchroeter.Dsl.DynamicProperties
{
    [Serializable]
    //Note: here we should use [TypeConverter (typeof(DynamicPropertyCollectionTypeConverter))] but
    //      since the known issue 1.10 reported at http://blogs.msdn.com/vsxteam/archive/2008/01/30/Known-Issues-for-Microsoft-Visual-Studio-2008-SDK-1.0.aspx
    //      the version with a string seems to be better at this time.
    //      see also: http://www.ticklishtechs.net/2008/02/06/workaround-for-known-issue-with-typeconverters-in-dsl-tools-for-visual-studio/
    [TypeConverter("BenjaminSchroeter.Dsl.DynamicProperties.DynamicPropertyCollectionTypeConverter")] 
    public class DynamicPropertyCollection :  ICustomTypeDescriptor, IEnumerable<DynamicProperty>
    {
        private readonly List<DynamicProperty> properties = new List<DynamicProperty>();

        public DynamicPropertyCollection()
        {
        }

        public void AddProperty(string name, Type type)
        {
            AddProperty(name,type,null);
        }

        public void AddProperty(string name, Type type, object value)
        {
            if ( type == null )
                throw new ArgumentNullException("type");

            if ( ExistsProperty(name) )
                throw new ArgumentException(string.Format("The given namen {0} already exists.", name));

            DynamicProperty p = new DynamicProperty {Name = name, Type = type, Value = value};
            properties.Add(p);
        }

        public void RemoveProperty(string name)
        {
            properties.RemoveAll(p => p.Name == name);
        }

        public bool ExistsProperty(string name)
        {
            DynamicProperty prop = properties.Find(p => p.Name == name);
            return prop != null;
        }

        public Type GetPropertyType(string name)
        {
            DynamicProperty prop = properties.Find(p => p.Name == name);

            if (prop == null)
                throw new KeyNotFoundException(string.Format("The given namen {0} does not exist as a property.", name));

            return prop.Type;
        }

        public object GetPropertyValue(string name)
        {
            DynamicProperty prop = properties.Find(p => p.Name == name);

            if (prop == null)
                throw new KeyNotFoundException(string.Format("The given namen {0} does not exist as a property.", name));

            return prop.Value;            
        }

        public void SetPropertyValue(string name, object value)
        {
            DynamicProperty prop = properties.Find(p => p.Name == name);

            if (prop == null)
                throw new KeyNotFoundException(string.Format("The given namen {0} does not exist as a property.", name));

            if (!prop.Type.IsAssignableFrom(value.GetType()))
                throw new ArgumentException(string.Format("The given value can not convert to {0}.", prop.Type));

            prop.Value = value;
        }

        #region ICustomTypeDescriptor Members
        ///<summary>
        ///Returns the properties for this instance of a component using the attribute array as a filter.
        ///</summary>
        ///<returns>
        ///A <see cref="T:System.ComponentModel.PropertyDescriptorCollection"></see> that represents the filtered properties for this component instance.
        ///</returns>
        ///<param name="attributes">An array of type <see cref="T:System.Attribute"></see> that is used as a filter. </param>
        public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
        {
            PropertyDescriptorCollection props = new PropertyDescriptorCollection(new PropertyDescriptor[] { });
            foreach (DynamicProperty dynPropDef in this.properties)
                props.Add(new DynamicPropertyDescriptor(dynPropDef, new Attribute[] { }));

            return props;
        }
        #endregion

        #region ICustomTypeDescriptor Members (default implementation)
        ///<summary>
        ///Returns the default property for this instance of a component.
        ///</summary>
        ///<returns>
        ///A <see cref="T:System.ComponentModel.PropertyDescriptor"></see> that represents the default property for this object, or null if this object does not have properties.
        ///</returns>
        public PropertyDescriptor GetDefaultProperty()
        {
            return TypeDescriptor.GetDefaultProperty(this, true);
            // Note: for later improvement we can add some code for a default property here
        }
        
        ///<summary>
        ///Returns a collection of custom attributes for this instance of a component.
        ///</summary>
        ///
        ///<returns>
        ///An <see cref="T:System.ComponentModel.AttributeCollection"></see> containing the attributes for this object.
        ///</returns>
        ///
        public AttributeCollection GetAttributes()
        {
            return TypeDescriptor.GetAttributes(this, true);
        }

        ///<summary>
        ///Returns the class name of this instance of a component.
        ///</summary>
        ///
        ///<returns>
        ///The class name of the object, or null if the class does not have a name.
        ///</returns>
        ///
        public string GetClassName()
        {
            return TypeDescriptor.GetClassName(this, true);
        }

        ///<summary>
        ///Returns the name of this instance of a component.
        ///</summary>
        ///
        ///<returns>
        ///The name of the object, or null if the object does not have a name.
        ///</returns>
        ///
        public string GetComponentName()
        {
            return TypeDescriptor.GetComponentName(this, true);
        }

        ///<summary>
        ///Returns a type converter for this instance of a component.
        ///</summary>
        ///
        ///<returns>
        ///A <see cref="T:System.ComponentModel.TypeConverter"></see> that is the converter for this object, or null if there is no <see cref="T:System.ComponentModel.TypeConverter"></see> for this object.
        ///</returns>
        ///
        public TypeConverter GetConverter()
        {
            return TypeDescriptor.GetConverter(this, true);
        }

        ///<summary>
        ///Returns the default event for this instance of a component.
        ///</summary>
        ///
        ///<returns>
        ///An <see cref="T:System.ComponentModel.EventDescriptor"></see> that represents the default event for this object, or null if this object does not have events.
        ///</returns>
        ///
        public EventDescriptor GetDefaultEvent()
        {
            return TypeDescriptor.GetDefaultEvent(this, true);
        }

        ///<summary>
        ///Returns an editor of the specified type for this instance of a component.
        ///</summary>
        ///
        ///<returns>
        ///An <see cref="T:System.Object"></see> of the specified type that is the editor for this object, or null if the editor cannot be found.
        ///</returns>
        ///
        ///<param name="editorBaseType">A <see cref="T:System.Type"></see> that represents the editor for this object. </param>
        public object GetEditor(Type editorBaseType)
        {
            return TypeDescriptor.GetEditor(this, editorBaseType, true);
        }

        ///<summary>
        ///Returns the events for this instance of a component.
        ///</summary>
        ///
        ///<returns>
        ///An <see cref="T:System.ComponentModel.EventDescriptorCollection"></see> that represents the events for this component instance.
        ///</returns>
        ///
        public EventDescriptorCollection GetEvents()
        {
            return TypeDescriptor.GetEvents(this, true);
        }

        ///<summary>
        ///Returns the events for this instance of a component using the specified attribute array as a filter.
        ///</summary>
        ///
        ///<returns>
        ///An <see cref="T:System.ComponentModel.EventDescriptorCollection"></see> that represents the filtered events for this component instance.
        ///</returns>
        ///
        ///<param name="attributes">An array of type <see cref="T:System.Attribute"></see> that is used as a filter. </param>
        public EventDescriptorCollection GetEvents(Attribute[] attributes)
        {
            return TypeDescriptor.GetEvents(this, attributes, true);
        }

        ///<summary>
        ///Returns the properties for this instance of a component.
        ///</summary>
        ///
        ///<returns>
        ///A <see cref="T:System.ComponentModel.PropertyDescriptorCollection"></see> that represents the properties for this component instance.
        ///</returns>
        ///
        public PropertyDescriptorCollection GetProperties()
        {
            return ((ICustomTypeDescriptor)this).GetProperties(new Attribute[0]);
        }

        ///<summary>
        ///Returns an object that contains the property described by the specified property descriptor.
        ///</summary>
        ///
        ///<returns>
        ///An <see cref="T:System.Object"></see> that represents the owner of the specified property.
        ///</returns>
        ///
        ///<param name="pd">A <see cref="T:System.ComponentModel.PropertyDescriptor"></see> that represents the property whose owner is to be found. </param>
        public object GetPropertyOwner(PropertyDescriptor pd)
        {
            return this;
        }
        #endregion

        #region IEnumerable Members
        ///<summary>
        ///Returns an enumerator that iterates through a collection.
        ///</summary>
        ///
        ///<returns>
        ///An <see cref="T:System.Collections.IEnumerator"></see> object that can be used to iterate through the collection.
        ///</returns>
        ///<filterpriority>2</filterpriority>
        public IEnumerator GetEnumerator()
        {
            return properties.GetEnumerator();
        }
        #endregion

        #region IEnumerable<DynamicProperty> Members
        ///<summary>
        ///Returns an enumerator that iterates through the collection.
        ///</summary>
        ///
        ///<returns>
        ///A <see cref="T:System.Collections.Generic.IEnumerator`1"></see> that can be used to iterate through the collection.
        ///</returns>
        ///<filterpriority>1</filterpriority>
        IEnumerator<DynamicProperty> IEnumerable<DynamicProperty>.GetEnumerator()
        {
            return properties.GetEnumerator();
        }
        #endregion
    }
}